feat: #110 Add pairing table assignments#111
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThis update removes the old multi-step round advancement wizard and drag-and-drop pairing grid, replacing them with a new, form-driven Tournament Pairings Page. The new implementation introduces sortable grid components, pairing validation, table assignment utilities, and confirmation dialogs, with extensive refactoring of pairing creation logic, error handling, and context menu actions. Numerous obsolete components, styles, and utilities are deleted, and related routes, exports, and service hooks are updated to reflect the new pairing workflow. Changes
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
npm error Exit handler never called! ✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
4ae7541 to
6294f35
Compare
There was a problem hiding this comment.
Actionable comments posted: 11
🔭 Outside diff range comments (2)
src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx (1)
84-95: Add key props to existing button elements.For consistency, the existing button elements in the array should also have key props.
return [ - <Button onClick={openTournamentCreateTeamDialog}> + <Button key="new-team" onClick={openTournamentCreateTeamDialog}> <UserPlus />New Team </Button>, ]; } return [ - <Button onClick={handleRegister}> + <Button key="register" onClick={handleRegister}> <UserPlus />Register </Button>, ];src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx (1)
7-11: RenameisUnassignedPairingInputto match the returned typeNo references to
UnassignedTournamentPairingremain in the codebase, and the guard now narrows toDraftTournamentPairing. To avoid confusion, update the function name (and any related imports/tests) accordingly:• File:
src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx(lines 7–11)-export function isUnassignedPairingInput(pairing: unknown): pairing is DraftTournamentPairing { +export function isDraftTournamentPairingInput(pairing: unknown): pairing is DraftTournamentPairing { return typeof pairing === 'object' && pairing !== null && 'tournamentCompetitor0Id' in pairing; }
🧹 Nitpick comments (16)
src/style/_variables.scss (1)
75-81: Expose complementary “info” tokens for visual parityYou introduced
--text-color-blueand--bg-blue, but there are no matching border or variant tokens (e.g.--border-color-blue,--primary-info-*, etc.). Down-stream components may fall back to gray, leading to inconsistent styling. Adding the missing counterparts keeps the API symmetrical with danger, warning, and success variants.src/components/generic/InfoPopover/InfoPopover.module.scss (1)
32-34: Nit: consider a CSS variable for the max-widthHard-coding
20remworks, but exposing it as--popover-max-width(defaulting to20rem) would allow design adjustments without recompiling Sass.- max-width: 20rem; + max-width: var(--popover-max-width, 20rem);src/components/generic/SortableGrid/SortableGrid.module.scss (1)
1-5: Optional: parameterise gap to match spacing scaleThe fixed
0.5remgap breaks if the design system spacing scale changes. Using an existing CSS variable (e.g.--grid-gap-xs) or a Sass variable keeps the grid aligned with global spacing decisions.src/components/generic/Pulsar/index.ts (1)
1-3: Export the props type for easier consumptionDown-stream code often needs the component’s props type for generics or
React.ComponentProps<typeof Pulsar>. Re-exporting it here streamlines imports.export { Pulsar, + type PulsarProps, } from './Pulsar';src/components/generic/SortableGrid/components/index.ts (1)
1-3: Consider aggregating related exports for better DX
SortableItemis only reachable via a deep path (.../SortableGrid/components). Re-exporting it fromsrc/components/generic/SortableGrid/index.tsas well would let consumers import bothSortableGridandSortableItemfrom the same module barrel, avoiding brittle relative paths.// src/components/generic/SortableGrid/index.ts export { SortableGrid, + SortableItem, } from './SortableGrid';convex/_model/tournamentPairings/queries/getTournamentPairingsByTournament.ts (1)
29-37: Consider extracting sorting logic into a dedicated helper.The inline sorting logic is correct and properly handles null table values. However, the AI summary mentions a
sortPairingsByTableutility was introduced. For consistency with the sorting refactoring pattern used in other queries (likesortTournamentCompetitorsByName), consider extracting this logic into a dedicated helper function.- return deepTournamentPairings.filter(notNullOrUndefined).sort((a, b) => { - if (a.table === null) { - return 1; - } - if (b.table === null) { - return -1; - } - return a.table - b.table; - }); + return deepTournamentPairings.filter(notNullOrUndefined).sort(sortPairingsByTable);src/components/TournamentContextMenu/TournamentContextMenu.tsx (1)
89-91: Address TODO comments for confirmation dialogs.Consider implementing confirmation dialogs for the "Delete" and "Publish" actions to maintain consistency with the new "Configure Round" confirmation pattern.
Would you like me to help implement confirmation dialogs for the Delete and Publish actions to maintain consistency?
src/components/generic/Pulsar/Pulsar.tsx (1)
25-28: Clean up commented code.The commented
preventDefault()line should either be removed if not needed or uncommented with a comment explaining why it's necessary.- onClick={onClick ? (e) => { - // e.preventDefault(); - onClick(e); - } : undefined} + onClick={onClick ? (e) => { + onClick(e); + } : undefined}Or if preventDefault is needed:
- onClick={onClick ? (e) => { - // e.preventDefault(); - onClick(e); - } : undefined} + onClick={onClick ? (e) => { + e.preventDefault(); // Prevent default behavior when pulsar is clicked + onClick(e); + } : undefined}src/components/generic/Pulsar/Pulsar.module.scss (1)
23-25: Consider making dimensions configurable.The fixed 40px dimensions work for the current use case, but consider making this configurable via CSS custom properties for better reusability.
- width: 40px; - height: 40px; + width: var(--pulsar-size, 40px); + height: var(--pulsar-size, 40px);src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx (1)
79-79: Remove redundant variable declaration.The
isOrganizervariable is already declared at line 51 and is accessible in this scope.- const isOrganizer = user && tournament.organizerUserIds.includes(user._id);convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts (1)
41-43: Competitor filtering logic could be more efficient.The nested
findoperation insidefilterresults in O(n²) complexity. Consider using a Set for better performance.- const activeCompetitors = rankedCompetitors.filter(({ id }) => ( - !!competitors.find((c) => c._id === id && c.active) - )); + const activeCompetitorIds = new Set(competitors.filter(c => c.active).map(c => c._id)); + const activeCompetitors = rankedCompetitors.filter(({ id }) => activeCompetitorIds.has(id));convex/_model/tournamentPairings/mutations/createTournamentPairings.ts (1)
30-41: Review error message consistency for pairing creation context.The error messages reference "CANNOT_OPEN_ROUND_ON_*" but this function is specifically about creating pairings, not opening rounds. Consider using more specific error messages like "CANNOT_CREATE_PAIRINGS_ON_DRAFT_TOURNAMENT" for better clarity.
src/components/generic/SortableGrid/components/SortableItem.tsx (1)
37-43: Consider extracting complex getNewIndex logic.The getNewIndex function contains array swapping logic that could be extracted for better readability and testability.
Consider extracting this logic:
+const getNewIndexForSwap = (id: UniqueIdentifier, items: UniqueIdentifier[], activeIndex: number, overIndex: number) => + arraySwap(items, activeIndex, overIndex).indexOf(id); const { attributes, isDragging, listeners, setNodeRef, transform, transition, } = useSortable({ id, - getNewIndex: ({ - id, - items, - activeIndex, - overIndex, - }) => arraySwap(items, activeIndex, overIndex).indexOf(id), + getNewIndex: ({ id, items, activeIndex, overIndex }) => + getNewIndexForSwap(id, items, activeIndex, overIndex), });src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.tsx (1)
47-77: Consider extracting validation logic for better maintainability.The validation logic inside the imperative handle is complex and could benefit from extraction into a separate validation function for better testability and maintainability.
Consider extracting the validation logic:
+const validateTournamentRound = ( + activeCompetitors: TournamentCompetitor[], + tournament: Tournament, + toast: ToastApi +): boolean => { + if (activeCompetitors.length < 2) { + toast.error('Cannot Configure Round', { + description: 'Please ensure at least 2 competitors are active.', + }); + return false; + } + // ... rest of validation logic + return true; +}; useImperativeHandle(ref, () => ({ open: () => { - if (activeCompetitors.length < 2) { - return toast.error('Cannot Configure Round', { - description: 'Please ensure at least 2 competitors are active.', - }); - } - // ... rest of validation + if (!validateTournamentRound(activeCompetitors, tournament, toast)) { + return; + } if (warnings.length) { open(); } else { proceed(); } }, }));src/components/generic/SortableGrid/SortableGrid.tsx (1)
88-88: Use a more idiomatic approach for creating repeated array values.Instead of using underscore for unused parameters, use a cleaner approach.
- gridTemplateColumns: Array.from({ length: columns }).map((_) => '1fr').join(' '), + gridTemplateColumns: Array.from({ length: columns }, () => '1fr').join(' '),src/pages/TournamentPairingsPage/TournamentPairingsPage.utils.tsx (1)
27-31: Complete or remove empty JSDoc comments.Multiple functions have empty JSDoc comments that should either be completed with proper documentation or removed.
Either add meaningful documentation:
/** - * - * @param pairings - * @returns + * Flattens an array of tournament pairings into a single array of competitor IDs. + * @param pairings - Array of tournament pairing objects + * @returns Array of competitor IDs with 'bye' for null competitors */Or remove the empty JSDoc blocks entirely.
Also applies to: 45-50, 129-134
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
convex/_generated/api.d.tsis excluded by!**/_generated/**package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (92)
convex/_model/fowV4/aggregateFowV4TournamentData.ts(1 hunks)convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts(3 hunks)convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts(1 hunks)convex/_model/tournamentCompetitors/queries/getTournamentCompetitors.ts(2 hunks)convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts(2 hunks)convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts(3 hunks)convex/_model/tournamentPairings/_helpers/generateTableAssignments.ts(0 hunks)convex/_model/tournamentPairings/_helpers/sortCompetitorPairs.ts(1 hunks)convex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts(1 hunks)convex/_model/tournamentPairings/index.ts(4 hunks)convex/_model/tournamentPairings/mutations/createTournamentPairings.ts(1 hunks)convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts(2 hunks)convex/_model/tournamentPairings/queries/getTournamentPairingsByTournament.ts(1 hunks)convex/_model/tournaments/mutations/openTournamentRound.ts(0 hunks)convex/tournamentPairings.ts(2 hunks)package.json(0 hunks)src/api.ts(1 hunks)src/components/ConfirmationDialog/ConfirmationDialog.hooks.ts(1 hunks)src/components/ConfirmationDialog/ConfirmationDialog.module.scss(1 hunks)src/components/ConfirmationDialog/ConfirmationDialog.tsx(3 hunks)src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts(1 hunks)src/components/TournamentContextMenu/TournamentContextMenu.tsx(4 hunks)src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.module.scss(1 hunks)src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.tsx(1 hunks)src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.utils.tsx(1 hunks)src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/index.ts(1 hunks)src/components/TournamentPairingRow/TournamentPairingRow.tsx(1 hunks)src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx(2 hunks)src/components/TournamentPairingsGrid/Draggable/Draggable.module.scss(0 hunks)src/components/TournamentPairingsGrid/Draggable/Draggable.tsx(0 hunks)src/components/TournamentPairingsGrid/Draggable/index.ts(0 hunks)src/components/TournamentPairingsGrid/Droppable/Droppable.module.scss(0 hunks)src/components/TournamentPairingsGrid/Droppable/Droppable.tsx(0 hunks)src/components/TournamentPairingsGrid/Droppable/index.ts(0 hunks)src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.module.scss(0 hunks)src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.tsx(0 hunks)src/components/TournamentPairingsGrid/PairableCompetitorCard/index.ts(0 hunks)src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.module.scss(0 hunks)src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.tsx(0 hunks)src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.utils.ts(0 hunks)src/components/TournamentPairingsGrid/PairingsGridRow/index.ts(0 hunks)src/components/TournamentPairingsGrid/TournamentPairingsGrid.module.scss(0 hunks)src/components/TournamentPairingsGrid/TournamentPairingsGrid.tsx(0 hunks)src/components/TournamentPairingsGrid/TournamentPairingsGrid.types.ts(0 hunks)src/components/TournamentPairingsGrid/TournamentPairingsGrid.utils.ts(0 hunks)src/components/TournamentPairingsGrid/index.ts(0 hunks)src/components/generic/InfoPopover/InfoPopover.module.scss(1 hunks)src/components/generic/InfoPopover/InfoPopover.tsx(3 hunks)src/components/generic/Pulsar/Pulsar.module.scss(1 hunks)src/components/generic/Pulsar/Pulsar.tsx(1 hunks)src/components/generic/Pulsar/index.ts(1 hunks)src/components/generic/SortableGrid/SortableGrid.module.scss(1 hunks)src/components/generic/SortableGrid/SortableGrid.tsx(1 hunks)src/components/generic/SortableGrid/components/SortableItem.module.scss(1 hunks)src/components/generic/SortableGrid/components/SortableItem.tsx(1 hunks)src/components/generic/SortableGrid/components/index.ts(1 hunks)src/components/generic/SortableGrid/index.ts(1 hunks)src/components/generic/Table/Table.tsx(1 hunks)src/components/generic/Table/Table.types.ts(1 hunks)src/components/generic/Table/TableCell.tsx(2 hunks)src/components/generic/Table/TableRow.tsx(2 hunks)src/components/generic/Warning/Warning.module.scss(1 hunks)src/components/generic/Warning/Warning.tsx(1 hunks)src/components/generic/Warning/index.ts(1 hunks)src/modals.ts(2 hunks)src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.hooks.ts(0 hunks)src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.tsx(0 hunks)src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss(0 hunks)src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx(0 hunks)src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.module.scss(0 hunks)src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.tsx(0 hunks)src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.utils.ts(0 hunks)src/pages/TournamentAdvanceRoundPage/components/PairingsStep/index.ts(0 hunks)src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.module.scss(0 hunks)src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.tsx(0 hunks)src/pages/TournamentAdvanceRoundPage/components/RosterStep/index.ts(0 hunks)src/pages/TournamentAdvanceRoundPage/index.ts(0 hunks)src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx(5 hunks)src/pages/TournamentDetailPage/index.ts(1 hunks)src/pages/TournamentPairingsPage/TournamentPairingsPage.module.scss(1 hunks)src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts(1 hunks)src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx(1 hunks)src/pages/TournamentPairingsPage/TournamentPairingsPage.utils.tsx(1 hunks)src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss(1 hunks)src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx(1 hunks)src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts(1 hunks)src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/index.ts(1 hunks)src/pages/TournamentPairingsPage/index.ts(1 hunks)src/routes.tsx(2 hunks)src/services/tournamentPairings.ts(2 hunks)src/settings.ts(1 hunks)src/style/_variables.scss(2 hunks)
💤 Files with no reviewable changes (33)
- src/components/TournamentPairingsGrid/Draggable/index.ts
- src/components/TournamentPairingsGrid/PairableCompetitorCard/index.ts
- src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.utils.ts
- src/pages/TournamentAdvanceRoundPage/index.ts
- src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.module.scss
- src/components/TournamentPairingsGrid/index.ts
- src/pages/TournamentAdvanceRoundPage/components/PairingsStep/index.ts
- src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.utils.ts
- src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss
- src/components/TournamentPairingsGrid/Droppable/index.ts
- package.json
- src/pages/TournamentAdvanceRoundPage/components/RosterStep/index.ts
- src/components/TournamentPairingsGrid/Draggable/Draggable.module.scss
- src/components/TournamentPairingsGrid/PairingsGridRow/index.ts
- src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.module.scss
- src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.module.scss
- src/components/TournamentPairingsGrid/TournamentPairingsGrid.types.ts
- src/components/TournamentPairingsGrid/Droppable/Droppable.module.scss
- convex/_model/tournaments/mutations/openTournamentRound.ts
- src/components/TournamentPairingsGrid/Draggable/Draggable.tsx
- src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.tsx
- src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.tsx
- src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.tsx
- src/components/TournamentPairingsGrid/Droppable/Droppable.tsx
- src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.hooks.ts
- src/components/TournamentPairingsGrid/TournamentPairingsGrid.module.scss
- src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.module.scss
- convex/_model/tournamentPairings/_helpers/generateTableAssignments.ts
- src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.tsx
- src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx
- src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.tsx
- src/components/TournamentPairingsGrid/TournamentPairingsGrid.utils.ts
- src/components/TournamentPairingsGrid/TournamentPairingsGrid.tsx
🧰 Additional context used
🧬 Code Graph Analysis (19)
convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts (2)
convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts (1)
DeepTournamentCompetitor(46-46)src/api.ts (1)
DeepTournamentCompetitor(33-33)
src/components/ConfirmationDialog/ConfirmationDialog.hooks.ts (1)
src/modals.ts (1)
useModal(28-37)
src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts (2)
src/api.ts (1)
TournamentCompetitorId(34-34)src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts (3)
createSchema(6-41)FormData(43-43)getDefaultValues(50-56)
src/components/generic/Table/TableCell.tsx (1)
src/components/generic/Table/Table.types.ts (1)
RowData(3-3)
convex/_model/tournamentPairings/index.ts (2)
src/api.ts (1)
ShallowTournamentPairing(39-39)convex/_generated/dataModel.d.ts (1)
Doc(30-33)
src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx (6)
src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts (1)
assignTables(4-74)src/components/generic/Table/Table.types.ts (1)
ColumnDef(5-13)src/components/TournamentPairingRow/TournamentPairingRow.tsx (1)
TournamentPairingRow(17-40)src/components/ConfirmationDialog/ConfirmationDialog.tsx (1)
ConfirmationDialog(29-73)src/components/generic/Table/Table.tsx (1)
Table(16-32)src/components/generic/Warning/Warning.tsx (1)
Warning(12-25)
convex/_model/fowV4/aggregateFowV4TournamentData.ts (1)
convex/_model/fowV4/flattenFowV4StatMap.ts (1)
flattenFowV4StatMap(14-33)
convex/tournamentPairings.ts (1)
convex/_model/tournamentPairings/mutations/createTournamentPairings.ts (1)
createTournamentPairings(20-56)
convex/_model/tournamentCompetitors/queries/getTournamentCompetitors.ts (1)
convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts (1)
sortTournamentCompetitorsByName(3-20)
convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts (2)
convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts (1)
deepenTournamentCompetitor(17-41)convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts (1)
sortTournamentCompetitorsByName(3-20)
src/components/generic/Table/Table.tsx (1)
src/components/generic/Table/TableRow.tsx (1)
TableRow(19-44)
convex/_model/tournamentPairings/queries/getTournamentPairingsByTournament.ts (1)
convex/_model/common/_helpers/notNullOrUndefined.ts (1)
notNullOrUndefined(1-3)
convex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts (3)
convex/_model/tournamentPairings/index.ts (3)
TournamentPairingDeep(18-18)ShallowTournamentPairing(13-13)DraftTournamentPairing(32-32)src/api.ts (3)
TournamentPairingDeep(40-40)ShallowTournamentPairing(39-39)DraftTournamentPairing(86-86)convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts (1)
DraftTournamentPairing(14-14)
src/components/generic/Table/TableRow.tsx (2)
src/components/generic/Table/Table.types.ts (1)
RowData(3-3)src/components/generic/Table/TableCell.tsx (1)
TableCell(14-43)
src/routes.tsx (3)
src/settings.ts (1)
PATHS(10-24)src/pages/TournamentPairingsPage/index.ts (1)
TournamentPairingsPage(2-2)src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx (1)
TournamentPairingsPage(54-263)
convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts (1)
convex/_generated/dataModel.d.ts (1)
Id(48-49)
convex/_model/tournamentPairings/_helpers/sortCompetitorPairs.ts (1)
convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts (1)
CompetitorPair(10-10)
src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx (1)
src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts (1)
TournamentPairingFormItem(17-17)
src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts (4)
src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts (1)
TournamentPairingFormItem(17-17)convex/_model/tournamentPairings/index.ts (1)
DraftTournamentPairing(32-32)convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts (1)
DraftTournamentPairing(14-14)src/api.ts (1)
DraftTournamentPairing(86-86)
🪛 Biome (1.9.4)
src/pages/TournamentPairingsPage/TournamentPairingsPage.utils.tsx
[error] 35-35: Avoid the use of spread (...) syntax on accumulators.
Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.
(lint/performance/noAccumulatingSpread)
src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx
[error] 98-98: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
🔇 Additional comments (84)
src/style/_variables.scss (1)
29-34: Blue palette import mirrors existing pattern – verify bundle size impact
@use-ing four additional Radix blue stylesheets increases the amount of generated CSS. If the blue palette is only used for a handful of utility variables, consider importing just the required tokens (or tree-shaking via PostCSS) to avoid bloating the final bundle.src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.module.scss (1)
1-5: LGTM – idiomatic namespaced mixin usageThe module correctly namespaces the
flexutilities and applies them viaflex.column; no issues spotted.src/components/generic/SortableGrid/index.ts (1)
1-3: Path looks correct – no issues spottedThe re-export is straightforward and matches the file structure.
src/pages/TournamentDetailPage/index.ts (1)
1-3: Barrel export is fine, but keep an eye on tree-shakingIf
TournamentDetailPageis large, ensure it’s lazily imported elsewhere (e.g., via React.lazy / dynamic import) so this barrel doesn’t accidentally pull it into initial bundles.src/pages/TournamentPairingsPage/index.ts (1)
1-3: LGTMSimple, correct re-export; nothing to add.
src/components/generic/Warning/index.ts (1)
1-2: Nice touch exporting the prop typeGood practice to expose
WarningPropsalongside the component – encourages proper typing by consumers.src/settings.ts (1)
22-22: ✅ Approved: Route change totournamentPairingsis complete with no leftover references
- Verified with ripgrep across *.ts, *.tsx, *.js, and *.jsx files: no occurrences of
tournamentAdvanceor/tournaments/:id/advanceremain.src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/index.ts (1)
3-3: LGTM! Standard TypeScript export pattern.The addition of the props type export follows established conventions and enables proper type checking for consumers of this component.
convex/_model/fowV4/aggregateFowV4TournamentData.ts (1)
134-142: Downstream impact verified – no further action required.All usages of
aggregateFowV4TournamentData(notably ingetTournamentRankings.ts) were inspected and there are no other filters or logic assuminggamesPlayed > 0. The ranking function handles zero‐game participants correctly (they simply sort to the bottom).src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/index.ts (1)
1-2: LGTM! Standard component export pattern.The export statements follow established TypeScript conventions and properly expose both the component and its props type.
src/services/tournamentPairings.ts (2)
2-2: LGTM! Import addition follows established patterns.The addition of
createMutationHookto the imports is correct and necessary for the new mutation hook export.
13-13: LGTM! Mutation hook follows established conventions.The new
useCreateTournamentPairingshook follows the established naming convention and pattern used throughout the codebase for mutation hooks.src/api.ts (1)
39-39: LGTM! Type export aligns with refactoring.The addition of
ShallowTournamentPairingexport is consistent with the broader refactoring to remove unassigned pairing constructs and use shallow document representations.convex/tournamentPairings.ts (2)
1-1: LGTM! Proper import addition for mutation functionality.The addition of
mutationimport enables the new mutation export functionality.
24-27: Verify that TODO validation items are addressed.The mutation implementation looks correct with proper authorization and basic validation. However, the implementation in
convex/_model/tournamentPairings/mutations/createTournamentPairings.ts(lines 28-33) contains several TODO comments for critical validation:
- Validation for missing or invalid pairings
- Validation for competitor count and active status
- Validation for duplicate round pairings
- Validation for proper player counts per competitor
These validations are important for data integrity and should be implemented before this feature is deployed to production.
src/components/ConfirmationDialog/ConfirmationDialog.hooks.ts (1)
9-9: LGTM! Improved API flexibility with optional ID.Making the
idparameter optional aligns with the pattern inuseModal(src/modals.ts:28-37) and improves the hook's usability by auto-generating IDs when not specified. This change is backwards compatible with existing code.src/routes.tsx (3)
29-29: LGTM! Simplified import path.The import path simplification for
TournamentDetailPageimproves code organization.
31-31: LGTM! Component replacement aligns with refactoring.The replacement of
TournamentAdvanceRoundPagewithTournamentPairingsPageis consistent with the broader refactoring described in the AI summary to replace the old advance round wizard with a new streamlined Tournament Pairings Page.
136-139: LGTM! Route configuration properly updated.The route configuration correctly uses the new
PATHS.tournamentPairingspath andTournamentPairingsPagecomponent. The relevant code snippets confirm the new component is properly implemented with tournament pairing setup and confirmation logic.convex/_model/tournamentCompetitors/queries/getTournamentCompetitors.ts (1)
3-3: LGTM: Clean refactoring to use centralized sorting logic.The extraction of sorting logic into the
sortTournamentCompetitorsByNamehelper improves code maintainability and ensures consistent sorting behavior across tournament competitor queries.Also applies to: 12-12
src/components/generic/Table/Table.tsx (1)
27-27: LGTM: Proper index propagation to table components.The addition of the
index={i}prop correctly enables row index access throughout the table rendering pipeline, aligning with theTableRowcomponent's interface.convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts (3)
4-4: LGTM: Good refactoring to use centralized sorting logic.The extraction of sorting logic into the
sortTournamentCompetitorsByNamehelper maintains consistency with the sorting refactoring pattern used across the codebase.Also applies to: 6-6, 28-28
10-10: LGTM: Well-implemented optional rankings feature.The optional rankings functionality is properly implemented with appropriate null checking and conditional logic.
Also applies to: 20-23
24-27: LGTM: Proper integration of ranking results with competitor data.The mapping logic correctly finds and includes ranking results for each competitor, handling cases where rankings might not be available.
src/components/TournamentPairingRow/TournamentPairingRow.tsx (1)
4-4: LGTM: Clean type cleanup removing unused pairing type.The removal of
UnassignedTournamentPairingfrom the import and props interface aligns with the broader refactoring that streamlines the pairing type system.Also applies to: 12-12
src/modals.ts (2)
3-3: LGTM: UUID import added for automatic ID generation.The uuid import is properly added to support the auto-generation feature in the useModal hook.
28-29: LGTM: Improved modal hook flexibility with automatic ID generation.The changes effectively enhance the useModal hook by:
- Making the key parameter optional
- Auto-generating unique IDs when no key is provided
- Maintaining backward compatibility
This is a clean improvement that reduces boilerplate for consumers while preserving the ability to use custom IDs when needed.
src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.utils.tsx (2)
18-18: Verify the round calculation logic change.The round calculation was changed from
(tournament.currentRound ?? 0) + 1to(tournament.lastRound ?? -1) + 2. This represents a significant change in how the current round is determined.Please ensure this aligns with the tournament data model changes and that the logic correctly calculates the round number for the pairing process.
19-19: LGTM: Clean refactor to destructure competitor arrays.The refactor to destructure
activeandinactivearrays directly fromsortCompetitorsByActiveimproves readability and eliminates the need for property access throughout the function. The logic remains functionally identical.Also applies to: 21-21, 25-25, 27-27, 33-33
convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts (1)
1-21: LGTM: Well-structured sorting utility with proper fallback handling.The sorting function is well-implemented with:
- Clear priority order (teamName → familyName → username → empty string)
- Proper null/undefined handling with optional chaining
- Locale-aware string comparison using
localeCompare- Clean helper function structure
The implementation correctly handles all edge cases and provides consistent sorting behavior across the codebase.
src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts (1)
52-55: LGTM: Improved player array initialization with proper sizing.The change ensures the players array always contains exactly
competitorSizeelements, which is crucial for:
- Form validation consistency
- UI rendering predictability
- Proper handling of existing competitor data
The implementation correctly maps existing player data while providing sensible defaults (
active: true,userId: '') for new positions.convex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts (1)
1-29: LGTM: Robust table sorting with proper null handling.The sorting utility is well-implemented with:
- Generic type constraint supporting multiple pairing types (
AnyPairing)- Proper null/undefined handling (placing them at the end)
- Clean sorting logic for valid table numbers
- Consistent return values for comparison function
The implementation correctly handles all edge cases and provides predictable sorting behavior for tournament pairings.
src/components/generic/Table/TableCell.tsx (1)
11-11: LGTM! Clean implementation of index prop.The addition of the required
indexprop and its usage in therenderCellfunction call is well-implemented and maintains type safety.Also applies to: 17-17, 30-30
src/components/generic/Table/TableRow.tsx (1)
16-16: Clarify the dual index handling logic.The component now handles two different indices:
- The
indexprop passed toTableRow(optional, defaults to -1)- The index
iextracted from therowtuple on line 35In line 39, you're passing the
indexprop toTableCell, not the row's own indexi. Please verify this is the intended behavior, as it could be confusing to have two different index concepts.Also applies to: 23-23, 39-39
src/components/generic/Warning/Warning.module.scss (1)
1-46: LGTM! Well-structured SCSS module.The Warning component styles are well-organized with:
- Proper use of utility mixins
- Clean CSS Grid layout with semantic grid areas
- Consistent use of CSS variables for theming
- Logical spacing and sizing
The grid template effectively separates icon, header, and body content areas.
src/components/generic/InfoPopover/InfoPopover.tsx (1)
13-13: LGTM! Clean implementation of orientation feature.The
orientationprop addition is well-implemented:
- Properly typed with specific string literals
- Sensible default value of 'vertical'
- Correctly maps to the
sideprop for dynamic positioningThis provides good flexibility for popover placement.
Also applies to: 22-22, 39-39
src/components/generic/Warning/Warning.tsx (1)
1-25: LGTM! Clean and well-structured warning component.The implementation follows React best practices with proper TypeScript interfaces, CSS modules, and the clsx utility for conditional styling. The component is reusable and maintains consistency with the generic component pattern used throughout the codebase.
src/components/TournamentContextMenu/TournamentContextMenu.tsx (5)
1-1: Good addition of useRef import for dialog management.The import is correctly added to support the new confirmation dialog functionality.
9-10: Proper import and typing for the confirmation dialog.The imports are correctly structured with both the component and its handle type, following TypeScript best practices.
78-78: Correctly typed ref for imperative dialog control.The ref is properly typed with
ConfirmConfigureRoundDialogHandleto enable imperative control of the dialog.
105-107: Improved UX with confirmation dialog.The change from direct navigation to showing a confirmation dialog improves the user experience by providing validation and confirmation before configuring a round.
130-138: Proper component structure with fragment wrapper.The fragment wrapper correctly accommodates both the popover menu and the confirmation dialog, maintaining clean component structure.
convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts (3)
1-1: Good addition of Id import for proper typing.The
Idimport is correctly added to support the new results parameter typing with tournament competitor IDs.
20-25: Well-structured optional results parameter.The optional
resultsparameter is well-typed with a clear interface that includes all necessary ranking and match data. The optional nature maintains backwards compatibility while extending functionality.
38-38: Correct spread operator usage for results integration.The spread operator correctly integrates the optional results data into the returned object, maintaining the existing structure while adding the new fields when provided.
src/components/generic/Pulsar/Pulsar.tsx (4)
13-19: Excellent use of forwardRef with proper TypeScript typing.The forwardRef implementation is correct and allows parent components to access the underlying DOM element while maintaining proper TypeScript typing.
5-11: Well-designed props interface with sensible defaults.The props interface is comprehensive and includes all necessary customization options with appropriate types and sensible defaults.
29-35: Good use of inline styles for dynamic sizing and conditional rendering.The inline styles for dynamic sizing and conditional pulse animation are appropriate for this type of visual component where the size needs to be configurable.
38-38: Proper displayName setting for debugging.Setting the displayName is good practice for debugging and development tools.
src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss (2)
1-4: Good use of SCSS imports for design system consistency.The imports properly utilize the design system's variables, flex mixins, and text utilities, ensuring consistency across the application.
5-49: Well-structured SCSS with proper BEM naming convention.The styles follow a clear BEM-like naming convention with the component name prefix. The use of CSS custom properties, mixins, and nested selectors creates maintainable and consistent styles that integrate well with the design system.
src/components/ConfirmationDialog/ConfirmationDialog.module.scss (1)
8-14: Clean and modern SCSS implementation.The use of data attributes for conditional styling and CSS custom properties for padding values follows modern CSS best practices. The flex column layout is appropriate for dialog body content.
src/components/generic/Pulsar/Pulsar.module.scss (1)
71-81: Well-designed pulse animation.The keyframe animation provides smooth scaling and fading effects. The scale factor of 3 with the comment explaining the visual effect is helpful for maintainability.
src/pages/TournamentPairingsPage/TournamentPairingsPage.module.scss (2)
24-32: Well-structured CSS Grid implementation.The grid layout with named areas provides excellent organization for the complex tournament pairings interface. The use of CSS custom properties for consistent spacing is a good practice.
68-76: Proper button styling within grid context.The full-width button styling within the grid item ensures consistent visual hierarchy and proper touch targets.
src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts (2)
6-9: Type casting pattern follows existing conventions.The union type with string transformation to
TournamentCompetitorIdis consistent with patterns used elsewhere in the codebase, as seen in the relevant code snippets.
11-15: Schema structure supports form requirements well.The draft tournament pairing schema properly handles nullable values for table assignments and competitor IDs, which aligns with the pairing workflow where these values start as null and get assigned through user interaction.
src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts (5)
4-9: The function signature and return type look correct.The function takes the appropriate inputs and returns the expected
DraftTournamentPairing[]type. The intersection withplayedTablesis properly typed.
15-15: Efficient table pool creation using array indices.The approach of creating available tables using
Array.from({ length: tableCount }, (_, i) => i)is clean and efficient. The filtering logic correctly excludes already used tables.
65-73: Correct sorting implementation for table assignments.The sorting logic correctly places null tables at the end and sorts numbered tables in ascending order. This ensures a consistent ordering for the UI.
38-44: Potential bug: table assignment from filtered pool.The logic finds an available table but doesn't handle the case where
tablecould benullwhen removing from the pool. This could cause issues if no tables are available.- const table = availableTables.find((table) => !playedTables.includes(table)) ?? availableTables[0] ?? null; + const table = availableTables.find((table) => !playedTables.includes(table)) ?? availableTables[0] ?? null; - // Remove assigned table from available pool: - const index = availableTables.indexOf(table); - if (index > -1) { - availableTables.splice(index, 1); - } + // Remove assigned table from available pool: + if (table !== null) { + const index = availableTables.indexOf(table); + if (index > -1) { + availableTables.splice(index, 1); + } + }Likely an incorrect or invalid review comment.
12-12: Noundefinedcase fortable
TheTournamentPairingFormItemschema definestableasz.union([z.number(), z.null()]), so it’s always present as either anumberornull—neverundefined. Your existing filterconst usedTables = new Set( pairings .filter((p) => p.table !== null && p.table > -1) .map((p) => p.table) );already safely excludes
nulland negative values.Likely an incorrect or invalid review comment.
convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts (2)
10-10: Type rename improves semantic clarity.The rename from
DraftTournamentPairingtoCompetitorPairbetter reflects what this type represents - a pair of competitors rather than a full pairing object. This aligns with the function's purpose of generating competitor pairs.
26-26: Function signatures correctly updated.Both
generateDraftPairingsandrecursivePairfunction signatures have been updated to use the newCompetitorPairtype consistently.Also applies to: 55-55
convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts (4)
6-6: New imports properly support the refactored functionality.The imports for
getTournamentRankings,TournamentCompetitorRanked, andsortCompetitorPairsare correctly added to support the new implementation.Also applies to: 10-10
13-14: Schema-based type definition follows best practices.Using
v.object(uniqueFields)to define the schema andInferto extract the type is a good pattern for type safety and consistency.
44-50: Simplified competitor ordering logic.The refactored approach using a single
orderedCompetitorsarray and pushing based on method is cleaner than the previous implementation.
51-57: Comprehensive output formatting with proper sorting.The chain of operations (generate → sort → map) creates a consistent output format. The mapping correctly includes all required fields with appropriate defaults.
src/components/ConfirmationDialog/ConfirmationDialog.tsx (2)
26-26: NewdisablePaddingprop enhances layout flexibility.The addition of the
disablePaddingprop with a sensible default offalseallows for better control over dialog content spacing without breaking existing usage.Also applies to: 38-38
59-61: Simplified body container with data attribute styling.The new implementation uses a CSS class with a data attribute to control padding, which is more maintainable than conditional rendering. This approach works well with the new
Warningcomponent mentioned in the AI summary.src/components/generic/SortableGrid/components/SortableItem.module.scss (5)
1-8: Well-structured imports and shadow definitions.The SCSS imports are properly organized and the shadow variables use modern CSS syntax with proper fallbacks. The calculated shadow based on scale is a nice touch for 3D depth perception.
14-17: Efficient 3D transforms using CSS custom properties.The use of CSS custom properties for transforms allows for smooth JavaScript-controlled animations. The
translate3dapproach enables hardware acceleration for better performance.
52-52: Good accessibility consideration with tap highlight removal.The
-webkit-tap-highlight-color: transparentprevents unwanted highlight colors on touch devices, which is appropriate for draggable elements.
59-66: Smooth overlay state transitions with proper visual feedback.The overlay state provides clear visual feedback with scale transform, cursor changes, and enhanced shadows. The animation timing function creates a pleasing bounce effect.
70-80: Well-implemented animation keyframes.The
popanimation provides smooth transitions between states with matching transform and shadow values. The cubic-bezier timing function creates a natural feel.src/components/generic/SortableGrid/components/SortableItem.tsx (2)
45-53: Ensure cursor cleanup on component unmount.The current implementation correctly returns a cleanup function, but consider adding a safety check to ensure the cursor is always reset even if the component unmounts unexpectedly.
The cleanup logic is correctly implemented with the return function in useEffect.
21-78: Well-implemented sortable component with proper performance optimizations.The component correctly uses memo and forwardRef for performance, properly handles the drag state, and manages the overlay functionality effectively.
convex/_model/tournamentPairings/index.ts (3)
3-3: LGTM - Clean import addition.Adding the Doc import alongside Id is appropriate for creating the new type alias.
49-53: Mutation exports correctly exposedThe
createTournamentPairingsmutation and itscreateTournamentPairingsArgsare properly exported inconvex/_model/tournamentPairings/index.tsand consumed throughout the codebase:
src/services/tournamentPairings.ts– hook usingapi.tournamentPairings.createTournamentPairingssrc/pages/TournamentPairingsPage/...– mutation invoked in the page componentconvex/_generated/api.d.ts– mutation and args included in generated API typesNo further changes required.
13-13: Verified ShallowTournamentPairing usage across the codebaseAll occurrences of
ShallowTournamentPairinghave been updated to import from the new alias:
src/api.ts– correctly re-exportsShallowTournamentPairingfromconvex/_model/tournamentPairingsconvex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts– correctly importsShallowTournamentPairingvia the indexNo outdated references remain.
src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.tsx (2)
58-70: Validate player count logic is correct.The validation logic for active players per competitor correctly checks both minimum and maximum constraints. The error messages are clear and actionable.
84-84: Round number calculation is correct as written.The
lastRoundfield is a 0-based index of the last completed round (usingundefined ?? -1for “no rounds yet”), and the UI adds 1 to get the upcoming round index and another 1 to convert to human-readable numbering. That is why everywhere you see:
- In the confirmation dialog and warnings util:
(tournament.lastRound ?? -1) + 2- In the context menu label:
(lastRound ?? -1) + 2- In the backend mutation:
(tournament.lastRound ?? -1) + 1to set the next round indexThese are all consistent: the backend uses
+1to compute the next round’s zero-based index, and the frontend uses+2to display that as “Round 1”, “Round 2”, etc. No changes are required.src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx (2)
3-5: Verify TournamentPairingFormItem import and usage.The new import for TournamentPairingFormItem looks correct and aligns with the form-based approach mentioned in the summary.
19-21: Function signature update supports new form approach.The updated function signature now supports the new TournamentPairingFormItem type, which aligns with the form-based pairing approach described in the summary.
| const aHasNull = a[0] === null || a[1] === null; | ||
| const bHasNull = b[0] === null || b[1] === null; |
There was a problem hiding this comment.
Fix unnecessary null check for first element of CompetitorPair.
According to the CompetitorPair type definition, the first element is always TournamentCompetitorRanked (never null), and only the second element can be null. The check a[0] === null will never be true and is unnecessary.
- const aHasNull = a[0] === null || a[1] === null;
- const bHasNull = b[0] === null || b[1] === null;
+ const aHasNull = a[1] === null;
+ const bHasNull = b[1] === null;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const aHasNull = a[0] === null || a[1] === null; | |
| const bHasNull = b[0] === null || b[1] === null; | |
| const aHasNull = a[1] === null; | |
| const bHasNull = b[1] === null; |
🤖 Prompt for AI Agents
In convex/_model/tournamentPairings/_helpers/sortCompetitorPairs.ts at lines 7
to 8, remove the null checks for the first element of the pairs (a[0] and b[0])
since the first element is guaranteed to never be null. Only check if the second
element (a[1] and b[1]) is null to correctly reflect the CompetitorPair type
definition.
src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx
Show resolved
Hide resolved
| export const getDefaultValues = (tournament?: Tournament | null): DeepPartial<FormData['pairings']> => tournament ? Array.from({ length: Math.ceil((tournament?.maxCompetitors ?? 24) / 2) }).map((_) => ({ | ||
| table: -1, | ||
| tournamentCompetitor0Id: null, | ||
| tournamentCompetitor1Id: null, | ||
| })) : []; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Simplify the default values function and clarify magic numbers.
The ternary operator can be simplified, and magic numbers should be documented for clarity.
-export const getDefaultValues = (tournament?: Tournament | null): DeepPartial<FormData['pairings']> => tournament ? Array.from({ length: Math.ceil((tournament?.maxCompetitors ?? 24) / 2) }).map((_) => ({
- table: -1,
- tournamentCompetitor0Id: null,
- tournamentCompetitor1Id: null,
-})) : [];
+export const getDefaultValues = (tournament?: Tournament | null): DeepPartial<FormData['pairings']> => {
+ if (!tournament) return [];
+
+ const DEFAULT_MAX_COMPETITORS = 24; // Fallback for tournaments without maxCompetitors
+ const UNASSIGNED_TABLE = -1; // Indicates table not yet assigned
+
+ const maxCompetitors = tournament.maxCompetitors ?? DEFAULT_MAX_COMPETITORS;
+ const pairingsCount = Math.ceil(maxCompetitors / 2);
+
+ return Array.from({ length: pairingsCount }).map(() => ({
+ table: UNASSIGNED_TABLE,
+ tournamentCompetitor0Id: null,
+ tournamentCompetitor1Id: null,
+ }));
+};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const getDefaultValues = (tournament?: Tournament | null): DeepPartial<FormData['pairings']> => tournament ? Array.from({ length: Math.ceil((tournament?.maxCompetitors ?? 24) / 2) }).map((_) => ({ | |
| table: -1, | |
| tournamentCompetitor0Id: null, | |
| tournamentCompetitor1Id: null, | |
| })) : []; | |
| export const getDefaultValues = ( | |
| tournament?: Tournament | null | |
| ): DeepPartial<FormData['pairings']> => { | |
| if (!tournament) return []; | |
| const DEFAULT_MAX_COMPETITORS = 24; // Fallback for tournaments without maxCompetitors | |
| const UNASSIGNED_TABLE = -1; // Indicates table not yet assigned | |
| const maxCompetitors = tournament.maxCompetitors ?? DEFAULT_MAX_COMPETITORS; | |
| const pairingsCount = Math.ceil(maxCompetitors / 2); | |
| return Array.from({ length: pairingsCount }).map(() => ({ | |
| table: UNASSIGNED_TABLE, | |
| tournamentCompetitor0Id: null, | |
| tournamentCompetitor1Id: null, | |
| })); | |
| }; |
🤖 Prompt for AI Agents
In src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts around
lines 25 to 29, simplify the getDefaultValues function by avoiding the ternary
operator and instead using a clearer conditional structure. Replace the magic
number 24 with a named constant or add a comment explaining its significance to
improve code clarity and maintainability.
convex/_model/tournamentPairings/mutations/createTournamentPairings.ts
Outdated
Show resolved
Hide resolved
| const { maxCompetitors } = useTournament(); | ||
|
|
||
| const assignedPairings = assignTables(pairings.filter((pairing) => ( | ||
| pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id | ||
| )).map((pairing) => ({ | ||
| ...pairing, | ||
| playedTables: Array.from(new Set([ | ||
| ...competitors.find((c) => c._id === pairing.tournamentCompetitor0Id)?.playedTables ?? [], | ||
| ...competitors.find((c) => c._id === pairing.tournamentCompetitor1Id)?.playedTables ?? [], | ||
| ])), | ||
| })), Math.ceil(maxCompetitors / 2)); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add null check for maxCompetitors and optimize competitor lookups.
The code assumes maxCompetitors is defined but doesn't handle the case where it might be null or undefined. Additionally, the competitor lookups in lines 32-33 are O(n) operations performed for each pairing, resulting in O(n²) complexity.
Consider this refactor to address both issues:
export const ConfirmPairingsDialog = ({
competitors,
id,
onConfirm,
pairings,
}: ConfirmPairingsDialogProps): JSX.Element => {
const { maxCompetitors } = useTournament();
+
+ // Create a map for O(1) competitor lookups
+ const competitorMap = new Map(competitors.map(c => [c._id, c]));
- const assignedPairings = assignTables(pairings.filter((pairing) => (
+ const filteredPairings = pairings.filter((pairing) => (
pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id
- )).map((pairing) => ({
+ ));
+
+ const enrichedPairings = filteredPairings.map((pairing) => ({
...pairing,
playedTables: Array.from(new Set([
- ...competitors.find((c) => c._id === pairing.tournamentCompetitor0Id)?.playedTables ?? [],
- ...competitors.find((c) => c._id === pairing.tournamentCompetitor1Id)?.playedTables ?? [],
+ ...competitorMap.get(pairing.tournamentCompetitor0Id)?.playedTables ?? [],
+ ...competitorMap.get(pairing.tournamentCompetitor1Id)?.playedTables ?? [],
])),
- })), Math.ceil(maxCompetitors / 2));
+ }));
+
+ const tableCount = maxCompetitors ? Math.ceil(maxCompetitors / 2) : 0;
+ const assignedPairings = assignTables(enrichedPairings, tableCount);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { maxCompetitors } = useTournament(); | |
| const assignedPairings = assignTables(pairings.filter((pairing) => ( | |
| pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id | |
| )).map((pairing) => ({ | |
| ...pairing, | |
| playedTables: Array.from(new Set([ | |
| ...competitors.find((c) => c._id === pairing.tournamentCompetitor0Id)?.playedTables ?? [], | |
| ...competitors.find((c) => c._id === pairing.tournamentCompetitor1Id)?.playedTables ?? [], | |
| ])), | |
| })), Math.ceil(maxCompetitors / 2)); | |
| export const ConfirmPairingsDialog = ({ | |
| competitors, | |
| id, | |
| onConfirm, | |
| pairings, | |
| }: ConfirmPairingsDialogProps): JSX.Element => { | |
| const { maxCompetitors } = useTournament(); | |
| // Create a map for O(1) competitor lookups | |
| const competitorMap = new Map(competitors.map(c => [c._id, c])); | |
| const filteredPairings = pairings.filter((pairing) => ( | |
| pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id | |
| )); | |
| const enrichedPairings = filteredPairings.map((pairing) => ({ | |
| ...pairing, | |
| playedTables: Array.from(new Set([ | |
| ...competitorMap.get(pairing.tournamentCompetitor0Id)?.playedTables ?? [], | |
| ...competitorMap.get(pairing.tournamentCompetitor1Id)?.playedTables ?? [], | |
| ])), | |
| })); | |
| const tableCount = maxCompetitors ? Math.ceil(maxCompetitors / 2) : 0; | |
| const assignedPairings = assignTables(enrichedPairings, tableCount); |
🤖 Prompt for AI Agents
In
src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx
around lines 25 to 35, add a null or undefined check for maxCompetitors before
using it to avoid runtime errors. Also, optimize competitor lookups by creating
a map or dictionary of competitors keyed by their _id before the pairing
processing loop, so you can perform O(1) lookups instead of O(n) searches for
each pairing. Then use this map to retrieve competitors when constructing
playedTables arrays, reducing overall complexity from O(n²) to O(n).
There was a problem hiding this comment.
Bad. This adds the beginning of the component again and has Typescript errors on lines 43 and 44.
There was a problem hiding this comment.
🧩 Analysis chain
@ianpaschal You're absolutely right - my previous suggestion was flawed. Let me provide a corrected version that only includes the necessary changes without duplicating the function signature and fixes the TypeScript issues.
🏁 Script executed:
# Let me check the specific lines around 43-44 to understand the TypeScript errors
sed -n '40,50p' src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx
Length of output: 405
Here's the corrected version that only includes the necessary changes and fixes the TypeScript issues:
const { maxCompetitors } = useTournament();
// Create a map for O(1) competitor lookups
const competitorMap = new Map(competitors.map(c => [c._id, c]));
const filteredPairings = pairings.filter((pairing) => (
pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id
));
const enrichedPairings = filteredPairings.map((pairing) => ({
...pairing,
playedTables: Array.from(new Set([
...(competitorMap.get(pairing.tournamentCompetitor0Id)?.playedTables ?? []),
...(competitorMap.get(pairing.tournamentCompetitor1Id)?.playedTables ?? []),
])),
}));
const tableCount = maxCompetitors ? Math.ceil(maxCompetitors / 2) : 0;
const assignedPairings = assignTables(enrichedPairings, tableCount);This replaces lines 25-35 in the original code. The key fixes:
- No duplication of function signature
- Added parentheses around the
competitorMap.get()calls to properly handle the potentialundefinedreturn value - Added null check for
maxCompetitorsbefore using it in the math calculation
🧬 Code Graph Analysis Results
Relevant snippets for understanding src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx and the user comment:
File: src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts
Lines: 4-74
Summary:
/**
* assignTables(pairings, tableCount): DraftTournamentPairing[]
*
* Assigns tables to tournament pairings based on availability and previously played tables.
*
* Parameters:
* - pairings: Array of TournamentPairingFormItem extended with playedTables (array of table numbers or null).
* - tableCount: Number of available tables.
*
* Returns:
* - Array of DraftTournamentPairing with tables assigned.
*
* Implementation details:
* - Collects tables already in use.
* - Creates a pool of available tables.
* - For each pairing:
* - If both competitors present (not a bye), assigns a table:
* - If pairing already has a valid table, keeps it.
* - Otherwise, assigns the best available table, preferring tables not played before by competitors.
* - If it is a bye (one competitor), assigns table as null.
* - Sorts pairings by table number, placing null tables last.
*/
export const assignTables = (
pairings: (TournamentPairingFormItem & {
playedTables: (number | null)[];
})[],
tableCount: number,
): DraftTournamentPairing[] => {
// Implementation as in snippet
};File: src/components/generic/Table/Table.types.ts
Lines: 5-13
Summary:
/**
* ColumnDef<T> type defines the structure of a column for a generic Table component.
*
* Properties:
* - key: string (unique key for the column)
* - label?: string (optional header label)
* - align?: 'left' | 'center' | 'right' (optional text alignment)
* - className?: string (optional CSS class)
* - width?: number (optional width in pixels)
* - renderCell?: (row: T, index: number) => ReactNode (optional custom cell renderer)
* - renderHeader?: () => ReactNode (optional custom header renderer)
*/
export type ColumnDef<T extends RowData> = {
align?: 'left' | 'center' | 'right';
className?: string;
key: string;
label?: string;
renderCell?: (row: T, index: number) => ReactNode;
renderHeader?: () => ReactNode;
width?: number;
};File: src/components/generic/Table/Table.tsx
Lines: 16-32
Summary:
/**
* Table component renders a table UI given columns and rows.
*
* Props:
* - columns: Array of ColumnDef<T>
* - rows: Array of T (row data)
* - className?: string (optional CSS class for table)
* - rowClassName?: string (optional CSS class for rows)
*
* Renders:
* - Header row using columns
* - Scrollable area with rows rendered using columns and optional custom cell renderers
*/
export const Table = <T extends RowData>({
className,
columns,
rowClassName,
rows,
}: TableProps<T>): JSX.Element => (
<div className={clsx(styles.Table, className)}>
<TableRow columns={columns} className={rowClassName} />
<ScrollArea className={styles.Table_ScrollArea}>
<div className={styles.Table_Inner}>
{rows.map((r, i) => (
<TableRow key={`row_${i}`} columns={columns} row={[r, i]} index={i} className={rowClassName} />
))}
</div>
</ScrollArea>
</div>
);File: src/components/TournamentPairingRow/TournamentPairingRow.tsx
Lines: 17-40
Summary:
/**
* TournamentPairingRow component renders a pairing row showing two competitors.
*
* Props:
* - pairing: DraftTournamentPairing (pairing data)
* - loading?: boolean (optional loading state)
* - className?: string (optional CSS class)
*
* Uses window width to determine orientation (vertical or horizontal).
* Uses IdentityBadge components to display competitor info.
*/
export const TournamentPairingRow = ({
pairing,
loading = false,
className,
}: TournamentPairingRowProps): JSX.Element => {
// Implementation as in snippet
};File: src/components/ConfirmationDialog/ConfirmationDialog.tsx
Lines: 29-73
Summary:
/**
* ConfirmationDialog component renders a modal dialog with confirm and cancel buttons.
*
* Props:
* - id: string (dialog identifier)
* - title: string (dialog title)
* - onConfirm?: () => void (callback on confirm)
* - disabled?: boolean (disable confirm button)
* - disablePadding?: boolean (disable padding inside dialog)
* - children: ReactNode (dialog content)
* - className?: string (optional CSS class)
* - description?: string (optional description text)
* - intent?: string (button intent style)
*
* Handles confirm by calling onConfirm and closing dialog.
*/
export const ConfirmationDialog = ({
children,
className,
description,
id,
intent = 'default',
onConfirm,
title,
disabled = false,
disablePadding = false,
}: ConfirmationDialogProps): JSX.Element => {
// Implementation as in snippet
};File: src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts
Line: 16
Summary:
/**
* TournamentPairingFormItem type inferred from draftTournamentPairingSchema using zod.
* Represents the form data structure for a tournament pairing.
*/
export type TournamentPairingFormItem = z.infer<typeof draftTournamentPairingSchema>;File: convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts
Line: 14
Summary:
/**
* DraftTournamentPairing type inferred from draftTournamentPairing schema.
* Represents a pairing with assigned table and competitor IDs.
*/
export type DraftTournamentPairing = Infer<typeof draftTournamentPairing>;These snippets provide context on types used (DraftTournamentPairing, TournamentPairingFormItem), the utility function assignTables used to assign tables to pairings, the generic Table and ColumnDef types used for rendering the table, the TournamentPairingRow component used to render each pairing, and the ConfirmationDialog component wrapping the dialog UI.
No snippet directly shows duplicated component beginnings or Typescript errors on lines 43 and 44, but the above are the relevant pieces to understand the component and its types.
✏️ Learnings added
Learnt from: ianpaschal
PR: ianpaschal/combat-command#111
File: src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx:25-35
Timestamp: 2025-07-08T06:35:15.834Z
Learning: When providing code suggestions, avoid including function signatures or component declarations that would duplicate existing code structure. Focus only on the lines that need to be changed or replaced within the existing function body.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
There was a problem hiding this comment.
This still doesn't work:
Argument of type 'TournamentCompetitorId | null' is not assignable to parameter of type 'Id<"tournamentCompetitors">'.
Type 'null' is not assignable to type 'Id<"tournamentCompetitors">'.
Type 'null' is not assignable to type 'string'.ts(2345)
There was a problem hiding this comment.
@ianpaschal You're right, the issue is that the Map expects non-null keys but the pairing competitor IDs can be null. Here's the fix with proper null checks:
const { maxCompetitors } = useTournament();
// Create a map for O(1) competitor lookups
const competitorMap = new Map(competitors.map(c => [c._id, c]));
const filteredPairings = pairings.filter((pairing) => (
pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id
));
const enrichedPairings = filteredPairings.map((pairing) => ({
...pairing,
playedTables: Array.from(new Set([
...(pairing.tournamentCompetitor0Id ? (competitorMap.get(pairing.tournamentCompetitor0Id)?.playedTables ?? []) : []),
...(pairing.tournamentCompetitor1Id ? (competitorMap.get(pairing.tournamentCompetitor1Id)?.playedTables ?? []) : []),
])),
}));
const tableCount = maxCompetitors ? Math.ceil(maxCompetitors / 2) : 0;
const assignedPairings = assignTables(enrichedPairings, tableCount);The key change is adding null checks (pairing.tournamentCompetitor0Id ? and pairing.tournamentCompetitor1Id ?) before calling competitorMap.get() to ensure we only pass valid non-null keys to the Map.
🧠 Learnings used
Learnt from: ianpaschal
PR: ianpaschal/combat-command#111
File: src/components/generic/Table/Table.types.ts:10-10
Timestamp: 2025-07-08T05:51:08.202Z
Learning: In TypeScript, adding optional parameters to function signatures is backward compatible. When a function type signature specifies more parameters than an implementation uses (e.g., changing `(row: T) => ReactNode` to `(row: T, index: number) => ReactNode`), it doesn't cause TypeScript errors. Functions can accept fewer parameters than allowed by the type, making such changes additive rather than breaking.
| const handleCancel = (_e: MouseEvent): void => { | ||
| // If dirty, open confirmation dialog | ||
| navigate(-1); | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Implement confirmation dialog for unsaved changes.
The comment indicates that a confirmation dialog should be shown when there are unsaved changes, but it's not implemented.
- const handleCancel = (_e: MouseEvent): void => {
- // If dirty, open confirmation dialog
- navigate(-1);
- };
+ const {
+ id: confirmCancelDialogId,
+ open: openConfirmCancelDialog,
+ } = useConfirmationDialog();
+
+ const handleCancel = (_e: MouseEvent): void => {
+ if (form.formState.isDirty) {
+ openConfirmCancelDialog({
+ onConfirm: () => navigate(-1),
+ });
+ } else {
+ navigate(-1);
+ }
+ };Don't forget to add the corresponding ConfirmationDialog component in the JSX.
Do you want me to generate the complete implementation including the dialog component?
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleCancel = (_e: MouseEvent): void => { | |
| // If dirty, open confirmation dialog | |
| navigate(-1); | |
| }; | |
| const { | |
| id: confirmCancelDialogId, | |
| open: openConfirmCancelDialog, | |
| } = useConfirmationDialog(); | |
| const handleCancel = (_e: MouseEvent): void => { | |
| if (form.formState.isDirty) { | |
| openConfirmCancelDialog({ | |
| onConfirm: () => navigate(-1), | |
| }); | |
| } else { | |
| navigate(-1); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx around lines 159
to 162, the handleCancel function should check if there are unsaved changes
(dirty state) and if so, display a confirmation dialog before navigating back.
Implement state to track the dialog visibility, add the ConfirmationDialog
component in the JSX, and update handleCancel to open the dialog instead of
immediately navigating. If the user confirms, then proceed with navigate(-1);
otherwise, close the dialog without navigating.
| // eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
| // @ts-ignore | ||
| onChange={handleChangePairingMethod} | ||
| options={tournamentPairingMethodOptions} | ||
| value={pairingMethod} |
There was a problem hiding this comment.
Fix TypeScript type issue instead of ignoring it.
The @ts-ignore comment indicates a type mismatch that should be fixed properly.
The issue likely stems from handleChangePairingMethod expecting a TournamentPairingMethod but InputSelect might be passing a different type. Consider:
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- onChange={handleChangePairingMethod}
+ onChange={(value) => handleChangePairingMethod(value as TournamentPairingMethod)}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
| // @ts-ignore | |
| onChange={handleChangePairingMethod} | |
| options={tournamentPairingMethodOptions} | |
| value={pairingMethod} | |
| onChange={(value) => handleChangePairingMethod(value as TournamentPairingMethod)} | |
| options={tournamentPairingMethodOptions} | |
| value={pairingMethod} |
🤖 Prompt for AI Agents
In src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx around lines 192
to 196, the @ts-ignore comment is used to bypass a TypeScript type error on the
onChange prop. Instead of ignoring the error, adjust the
handleChangePairingMethod function or the onChange handler to accept the correct
type that InputSelect passes, ensuring the types align properly. This may
involve updating the function signature or adding a type guard or conversion to
match the expected TournamentPairingMethod type.
| const params = useParams(); | ||
| const navigate = useNavigate(); | ||
|
|
||
| const tournamentId = params.id! as TournamentId; // Must exist or else how did we get to this route? |
There was a problem hiding this comment.
Handle the null case for params.id safely.
Using non-null assertion (!) is risky. Even though the comment suggests it must exist, it's better to handle edge cases gracefully.
- const tournamentId = params.id! as TournamentId; // Must exist or else how did we get to this route?
+ const tournamentId = params.id as TournamentId | undefined;
+
+ if (!tournamentId) {
+ navigate(PATHS.tournaments);
+ return <div>Invalid tournament ID</div>;
+ }🤖 Prompt for AI Agents
In src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx at line 58, avoid
using the non-null assertion operator on params.id. Instead, add a conditional
check to handle the case when params.id is null or undefined gracefully, such as
returning an error message or redirecting, to ensure the app does not crash
unexpectedly.
src/pages/TournamentPairingsPage/TournamentPairingsPage.utils.tsx
Outdated
Show resolved
Hide resolved
7347ed8 to
4c044c1
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/components/TournamentContextMenu/TournamentContextMenu.tsx (2)
37-37: Remove unused navigate import.The
navigatefunction is imported but no longer used since the "Configure Round" menu item now opens a dialog instead of navigating to a route.-import { generatePath, useNavigate } from 'react-router-dom'; +import { generatePath } from 'react-router-dom';- const navigate = useNavigate();
73-75: Toast message formatting is inconsistent.The toast message uses inconsistent formatting compared to other success messages. Consider using a consistent format with the tournament title.
- toast.success(`${title} started!`); + toast.success(`${title} has started!`);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
convex/_generated/api.d.tsis excluded by!**/_generated/**package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (102)
convex/_model/fowV4/aggregateFowV4TournamentData.ts(1 hunks)convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts(3 hunks)convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts(1 hunks)convex/_model/tournamentCompetitors/queries/getTournamentCompetitors.ts(2 hunks)convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts(2 hunks)convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts(3 hunks)convex/_model/tournamentPairings/_helpers/generateTableAssignments.ts(0 hunks)convex/_model/tournamentPairings/_helpers/sortCompetitorPairs.ts(1 hunks)convex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts(1 hunks)convex/_model/tournamentPairings/index.ts(4 hunks)convex/_model/tournamentPairings/mutations/createTournamentPairings.ts(1 hunks)convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts(2 hunks)convex/_model/tournamentPairings/queries/getTournamentPairingsByTournament.ts(1 hunks)convex/_model/tournamentTimers/mutations/createTournamentTimer.ts(1 hunks)convex/_model/tournaments/README.md(1 hunks)convex/_model/tournaments/index.ts(2 hunks)convex/_model/tournaments/mutations/endTournamentRound.ts(1 hunks)convex/_model/tournaments/mutations/startTournamentRound.ts(2 hunks)convex/_model/tournaments/queries/getTournamentOpenRound.ts(1 hunks)convex/common/errors.ts(1 hunks)convex/tournamentPairings.ts(2 hunks)convex/tournaments.ts(1 hunks)package.json(0 hunks)src/api.ts(1 hunks)src/components/ConfirmationDialog/ConfirmationDialog.hooks.ts(1 hunks)src/components/ConfirmationDialog/ConfirmationDialog.module.scss(1 hunks)src/components/ConfirmationDialog/ConfirmationDialog.tsx(3 hunks)src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts(1 hunks)src/components/TournamentContextMenu/TournamentContextMenu.tsx(7 hunks)src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.module.scss(1 hunks)src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.tsx(1 hunks)src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.utils.tsx(1 hunks)src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/index.ts(1 hunks)src/components/TournamentContextMenu/index.ts(1 hunks)src/components/TournamentPairingRow/TournamentPairingRow.tsx(1 hunks)src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx(2 hunks)src/components/TournamentPairingsGrid/Draggable/Draggable.module.scss(0 hunks)src/components/TournamentPairingsGrid/Draggable/Draggable.tsx(0 hunks)src/components/TournamentPairingsGrid/Draggable/index.ts(0 hunks)src/components/TournamentPairingsGrid/Droppable/Droppable.module.scss(0 hunks)src/components/TournamentPairingsGrid/Droppable/Droppable.tsx(0 hunks)src/components/TournamentPairingsGrid/Droppable/index.ts(0 hunks)src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.module.scss(0 hunks)src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.tsx(0 hunks)src/components/TournamentPairingsGrid/PairableCompetitorCard/index.ts(0 hunks)src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.module.scss(0 hunks)src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.tsx(0 hunks)src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.utils.ts(0 hunks)src/components/TournamentPairingsGrid/PairingsGridRow/index.ts(0 hunks)src/components/TournamentPairingsGrid/TournamentPairingsGrid.module.scss(0 hunks)src/components/TournamentPairingsGrid/TournamentPairingsGrid.tsx(0 hunks)src/components/TournamentPairingsGrid/TournamentPairingsGrid.types.ts(0 hunks)src/components/TournamentPairingsGrid/TournamentPairingsGrid.utils.ts(0 hunks)src/components/TournamentPairingsGrid/index.ts(0 hunks)src/components/generic/InfoPopover/InfoPopover.module.scss(1 hunks)src/components/generic/InfoPopover/InfoPopover.tsx(3 hunks)src/components/generic/Pulsar/Pulsar.module.scss(1 hunks)src/components/generic/Pulsar/Pulsar.tsx(1 hunks)src/components/generic/Pulsar/index.ts(1 hunks)src/components/generic/SortableGrid/SortableGrid.module.scss(1 hunks)src/components/generic/SortableGrid/SortableGrid.tsx(1 hunks)src/components/generic/SortableGrid/components/SortableItem.module.scss(1 hunks)src/components/generic/SortableGrid/components/SortableItem.tsx(1 hunks)src/components/generic/SortableGrid/components/index.ts(1 hunks)src/components/generic/SortableGrid/index.ts(1 hunks)src/components/generic/Table/Table.tsx(1 hunks)src/components/generic/Table/Table.types.ts(1 hunks)src/components/generic/Table/TableCell.tsx(2 hunks)src/components/generic/Table/TableRow.tsx(2 hunks)src/components/generic/Warning/Warning.module.scss(1 hunks)src/components/generic/Warning/Warning.tsx(1 hunks)src/components/generic/Warning/index.ts(1 hunks)src/modals.ts(2 hunks)src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.hooks.ts(0 hunks)src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.tsx(0 hunks)src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss(0 hunks)src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx(0 hunks)src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.module.scss(0 hunks)src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.tsx(0 hunks)src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.utils.ts(0 hunks)src/pages/TournamentAdvanceRoundPage/components/PairingsStep/index.ts(0 hunks)src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.module.scss(0 hunks)src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.tsx(0 hunks)src/pages/TournamentAdvanceRoundPage/components/RosterStep/index.ts(0 hunks)src/pages/TournamentAdvanceRoundPage/index.ts(0 hunks)src/pages/TournamentDetailPage/components/TournamentPairingsCard/TournamentPairingsCard.tsx(2 hunks)src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx(5 hunks)src/pages/TournamentDetailPage/index.ts(1 hunks)src/pages/TournamentPairingsPage/TournamentPairingsPage.module.scss(1 hunks)src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts(1 hunks)src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx(1 hunks)src/pages/TournamentPairingsPage/TournamentPairingsPage.utils.tsx(1 hunks)src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss(1 hunks)src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx(1 hunks)src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts(1 hunks)src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/index.ts(1 hunks)src/pages/TournamentPairingsPage/index.ts(1 hunks)src/routes.tsx(2 hunks)src/services/tournamentPairings.ts(2 hunks)src/services/tournaments.ts(1 hunks)src/settings.ts(1 hunks)src/style/_variables.scss(2 hunks)
💤 Files with no reviewable changes (32)
- package.json
- src/components/TournamentPairingsGrid/Draggable/index.ts
- src/components/TournamentPairingsGrid/Droppable/index.ts
- src/pages/TournamentAdvanceRoundPage/index.ts
- src/components/TournamentPairingsGrid/Draggable/Draggable.module.scss
- src/components/TournamentPairingsGrid/PairableCompetitorCard/index.ts
- src/components/TournamentPairingsGrid/PairingsGridRow/index.ts
- src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss
- src/pages/TournamentAdvanceRoundPage/components/PairingsStep/index.ts
- src/pages/TournamentAdvanceRoundPage/components/RosterStep/index.ts
- src/components/TournamentPairingsGrid/index.ts
- src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.module.scss
- src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.module.scss
- src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.utils.ts
- src/components/TournamentPairingsGrid/TournamentPairingsGrid.module.scss
- src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.tsx
- src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.module.scss
- src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.tsx
- src/components/TournamentPairingsGrid/TournamentPairingsGrid.types.ts
- src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx
- src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.tsx
- src/components/TournamentPairingsGrid/Draggable/Draggable.tsx
- src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.tsx
- src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.hooks.ts
- src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.module.scss
- src/components/TournamentPairingsGrid/Droppable/Droppable.module.scss
- src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.utils.ts
- src/components/TournamentPairingsGrid/Droppable/Droppable.tsx
- src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.tsx
- convex/_model/tournamentPairings/_helpers/generateTableAssignments.ts
- src/components/TournamentPairingsGrid/TournamentPairingsGrid.tsx
- src/components/TournamentPairingsGrid/TournamentPairingsGrid.utils.ts
✅ Files skipped from review due to trivial changes (11)
- src/components/generic/InfoPopover/InfoPopover.module.scss
- src/components/generic/SortableGrid/index.ts
- src/components/TournamentContextMenu/index.ts
- src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/index.ts
- src/settings.ts
- convex/_model/tournaments/README.md
- convex/_model/tournaments/mutations/endTournamentRound.ts
- src/components/TournamentPairingRow/TournamentPairingRow.tsx
- src/style/_variables.scss
- convex/tournaments.ts
- src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx
🚧 Files skipped from review as they are similar to previous changes (51)
- src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.module.scss
- src/components/generic/Pulsar/index.ts
- convex/_model/fowV4/aggregateFowV4TournamentData.ts
- src/pages/TournamentDetailPage/index.ts
- src/components/generic/Warning/index.ts
- src/components/generic/SortableGrid/SortableGrid.module.scss
- src/api.ts
- convex/tournamentPairings.ts
- src/pages/TournamentPairingsPage/index.ts
- convex/_model/tournamentPairings/queries/getTournamentPairingsByTournament.ts
- convex/_model/tournamentCompetitors/queries/getTournamentCompetitors.ts
- src/components/generic/Table/Table.tsx
- src/services/tournamentPairings.ts
- src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/index.ts
- src/routes.tsx
- convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts
- convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts
- src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts
- src/components/ConfirmationDialog/ConfirmationDialog.hooks.ts
- src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.utils.tsx
- convex/_model/tournamentPairings/_helpers/sortCompetitorPairs.ts
- convex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts
- src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss
- src/modals.ts
- src/components/generic/Warning/Warning.tsx
- src/components/generic/Table/Table.types.ts
- src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts
- src/components/generic/InfoPopover/InfoPopover.tsx
- convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts
- src/components/generic/Warning/Warning.module.scss
- src/components/generic/Pulsar/Pulsar.module.scss
- src/components/generic/Table/TableCell.tsx
- convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts
- src/components/generic/Table/TableRow.tsx
- src/components/ConfirmationDialog/ConfirmationDialog.tsx
- src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.tsx
- src/pages/TournamentPairingsPage/TournamentPairingsPage.module.scss
- convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts
- src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts
- src/components/ConfirmationDialog/ConfirmationDialog.module.scss
- convex/_model/tournamentPairings/index.ts
- src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx
- src/components/generic/Pulsar/Pulsar.tsx
- convex/_model/tournamentPairings/mutations/createTournamentPairings.ts
- src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx
- src/components/generic/SortableGrid/components/SortableItem.tsx
- src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx
- src/components/generic/SortableGrid/SortableGrid.tsx
- src/pages/TournamentPairingsPage/TournamentPairingsPage.utils.tsx
- src/components/generic/SortableGrid/components/SortableItem.module.scss
- src/components/generic/SortableGrid/components/index.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/TournamentContextMenu/TournamentContextMenu.tsx (3)
src/services/tournaments.ts (3)
useGetTournamentOpenRound(9-9)useStartTournamentRound(23-23)useEndTournamentRound(20-20)src/services/tournamentPairings.ts (1)
useGetTournamentPairings(6-6)src/components/generic/Button/Button.tsx (1)
Button(29-60)
🪛 Biome (1.9.4)
src/pages/TournamentDetailPage/components/TournamentPairingsCard/TournamentPairingsCard.tsx
[error] 50-55: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
🔇 Additional comments (34)
convex/common/errors.ts (1)
76-87: Well-structured error constants for pairing validation.The new error constants provide comprehensive coverage for pairing creation scenarios and follow consistent naming conventions. The error messages are clear and user-friendly, which will help with debugging and user feedback.
src/services/tournaments.ts (1)
20-23: Improved semantic clarity with tournament round lifecycle naming.The renaming from "open/close" to "start/end" for tournament rounds provides better semantic clarity and aligns with intuitive understanding of tournament lifecycle operations.
convex/_model/tournamentTimers/mutations/createTournamentTimer.ts (1)
39-41: Improved UX by starting timers immediately upon creation.The change to initialize timers in a started state rather than paused eliminates unnecessary manual intervention and provides a smoother tournament management experience.
convex/_model/tournaments/queries/getTournamentOpenRound.ts (1)
49-50: Corrected semantic meaning of match results progress fields.The swap correctly assigns
requiredto represent total expected match results andsubmittedto represent actual recorded results, fixing the semantic inconsistency and improving progress tracking clarity.convex/_model/tournaments/index.ts (1)
36-38: Updated exports align with tournament round lifecycle renaming.The export updates properly reflect the mutation renaming from "close/open" to "end/start", maintaining API consistency and supporting the improved tournament lifecycle semantics.
Also applies to: 47-50
src/components/TournamentContextMenu/TournamentContextMenu.tsx (13)
1-1: Import addition looks good.The
useRefimport is necessary for the new dialog management pattern being introduced.
9-10: Dialog component imports are properly structured.The imports for
ConfirmConfigureRoundDialogand its handle type are correctly imported from the appropriate location.
13-13: Tournament pairings hook import is appropriate.The
useGetTournamentPairingshook is needed for checking if pairings exist for the next round to determine menu item visibility.
17-17: Mutation hook names have been correctly updated.The hooks have been renamed from
useOpenTournamentRound/useCloseTournamentRoundtouseEndTournamentRound/useStartTournamentRound, which better reflects their actual functionality.Also applies to: 21-21
48-50: Round calculation logic is clear and correct.The next round calculation and labeling logic properly handles the tournament state and provides appropriate labels for UI display.
53-56: Next round pairings data fetching is well-implemented.The hook correctly fetches pairings for the next round to determine if the "Start Round" menu item should be visible.
77-81: Start tournament round mutation is properly implemented.The mutation correctly uses the new hook name and displays an appropriate success message with the current round label.
89-93: End tournament round mutation is correctly updated.The mutation has been properly renamed and the success message reflects the correct action.
95-95: Dialog ref initialization is correct.The ref is properly typed and initialized for controlling the confirmation dialog.
121-124: Configure Round menu item logic is well-designed.The menu item correctly opens the dialog instead of navigating, and the visibility logic properly checks for no open round and no existing pairings.
125-129: Start Round menu item implementation is solid.The new menu item correctly appears when pairings exist for the next round and uses the appropriate mutation to start the round.
131-134: End Round menu item is properly updated.The label and mutation have been correctly updated to reflect "End Round" instead of "Close Round".
151-159: Dialog integration is properly implemented.The component correctly renders the dialog alongside the popover menu, and the fragment wrapper is appropriate for the multiple return elements.
src/pages/TournamentDetailPage/components/TournamentPairingsCard/TournamentPairingsCard.tsx (10)
1-5: Import additions are appropriate.The added imports (
ReactElement,useRef,useState) are all used in the component and necessary for the new functionality.
9-9: Button import is necessary.The
Buttoncomponent is used in the empty state for the configure action.
12-12: Dialog component import is consistent.The import matches the pattern used in the context menu component.
30-31: Round index calculation is correct.The logic properly creates an array of round indexes based on
lastRoundand initializes the selected round to the latest round.
37-37: Dialog ref initialization is consistent.The ref follows the same pattern as the context menu component.
44-47: Round options generation is correct.The mapping properly creates options for the dropdown with appropriate labels.
54-54: Disabled condition is improved.The condition now properly disables the dropdown when there are fewer than 2 rounds, which makes sense for the user experience.
58-60: Configure handler is properly implemented.The handler correctly opens the dialog using the ref.
63-88: Dialog integration and empty state are well-implemented.The component correctly renders the dialog and includes a configure button in the empty state, providing a clear user action when no pairings exist.
28-28: lastRound usage in TournamentPairingsCard is correct—no change needed
This component needs to enumerate all completed pairing rounds plus the next pending one, so using lastRound to build the rounds list is intentional. currentRound reflects the active round elsewhere (timer, banner, roster) and isn’t appropriate here.convex/_model/tournaments/mutations/startTournamentRound.ts (6)
13-15: Args schema simplification is appropriate.The removal of
unassignedPairingsfrom the schema aligns with the separation of concerns where pairing creation is now handled by a separate mutation.
17-26: Function documentation is accurate.The updated JSDoc correctly describes the function's simplified purpose and parameters.
24-24: Function name change is semantically correct.The rename from
openTournamentRoundtostartTournamentRoundbetter reflects the function's purpose of starting a round with a timer.
50-54: Timer creation logic is correct.The function properly creates a tournament timer for the next round, which is the primary action when starting a round.
56-60: Round state update is appropriate.The function correctly updates the tournament's
currentRoundto the next round number, maintaining the tournament state consistency.
34-45: Error handling validation is comprehensive.The validation logic properly checks all tournament states that would prevent opening a round, maintaining data integrity.
| const getPrimaryButtons = (): ReactElement[] | undefined => [ | ||
| <InputSelect | ||
| options={roundOptions} | ||
| value={round} | ||
| onChange={(selected) => setRound(selected as number)} | ||
| disabled={showLoadingState || showEmptyState} | ||
| disabled={showLoadingState || roundIndexes.length < 2} | ||
| />, | ||
| ]; |
There was a problem hiding this comment.
Fix missing key prop in JSX array.
The static analysis correctly identified that the array element is missing a key prop, which is required for React's reconciliation.
- const getPrimaryButtons = (): ReactElement[] | undefined => [
- <InputSelect
- options={roundOptions}
- value={round}
- onChange={(selected) => setRound(selected as number)}
- disabled={showLoadingState || roundIndexes.length < 2}
- />,
- ];
+ const getPrimaryButtons = (): ReactElement[] | undefined => [
+ <InputSelect
+ key="round-select"
+ options={roundOptions}
+ value={round}
+ onChange={(selected) => setRound(selected as number)}
+ disabled={showLoadingState || roundIndexes.length < 2}
+ />,
+ ];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const getPrimaryButtons = (): ReactElement[] | undefined => [ | |
| <InputSelect | |
| options={roundOptions} | |
| value={round} | |
| onChange={(selected) => setRound(selected as number)} | |
| disabled={showLoadingState || showEmptyState} | |
| disabled={showLoadingState || roundIndexes.length < 2} | |
| />, | |
| ]; | |
| const getPrimaryButtons = (): ReactElement[] | undefined => [ | |
| <InputSelect | |
| key="round-select" | |
| options={roundOptions} | |
| value={round} | |
| onChange={(selected) => setRound(selected as number)} | |
| disabled={showLoadingState || roundIndexes.length < 2} | |
| />, | |
| ]; |
🧰 Tools
🪛 Biome (1.9.4)
[error] 50-55: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
🤖 Prompt for AI Agents
In
src/pages/TournamentDetailPage/components/TournamentPairingsCard/TournamentPairingsCard.tsx
around lines 49 to 56, the JSX array returned by getPrimaryButtons is missing a
key prop on the InputSelect element. Add a unique key prop to the InputSelect
component to satisfy React's requirement for keys in arrays, which helps with
efficient reconciliation.
* Update update-project-status.yml * Update updateProjectStatus.js * fix: Toast text does not wrap (#87) * fix: #86 Sanitize sign in/sign up inputs (#91) * feat: #32 Auto generate avatars & refactor users (#90) * feat: Improve <TournamentDetailPage/> default tab * feat: Improve <TournamentCard/> styling * Update mockData.ts * feat: #94 Improve <AccordionItem/> disabled state (#97) * feat: #95 Hide completed pairings from match check-in (#96) * bug: #93 Preserve <TournamentPairingsGrid/> internal state (#98) * feat: #101 Add player count to roster (#103) * feat: Show full player names when tournaments require it * feat: Add activePlayerCount to deep tournaments * feat: Sort tournament competitors by name * task: #100 Clean-up .card mixin (#102) * feat: #99 Improve tournament competitor edit dialog (#104) * feat: #106 Improve signIn error handling (#107) * Update convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts * Update convex/_model/users/_helpers/checkUserTournamentForcedName.ts * feat: Hide players with 0 matches from rankings * feat: #112 Add more mercenary team options (#113) * feat: #110 Add manual table assignments (#111) * fix: Ensure round 0 rankings can be included * Refactor tournament actions (#114) * refactor: Improve tournament actions * chore: Clean-up Convex errors * fix: Do not try to clean up current round timer on tournament end * fix: Don't allow players to be removed from tournament * chore: Update test tournament banner image * Update TournamentCard.tsx * fix: Ensure round 0 rankings can be included * fix: Fix end tournament round context menu behavior * chore: Improve mock match result creation * feat: Allow matchResult.playedAt to be date string or number * feat: #115 Hide match result battle plans (#116) * feat: Set page title based on <PageWrapper/> title prop * fix: Use <IdentityBadge/> to fix player name spacing on match results * fix: Correctly include match results relevant to a tournament
* Update update-project-status.yml * Update updateProjectStatus.js * fix: Toast text does not wrap (#87) * fix: Sanitize sign in/sign up inputs (#91) #86 * feat: #32 Auto generate avatars & refactor users (#90) * feat: Improve <TournamentDetailPage/> default tab * feat: Improve <TournamentCard/> styling * Update mockData.ts * feat: Improve <AccordionItem/> disabled state (#97) #94 * feat: Hide completed pairings from match check-in (#96) #95 * bug: Preserve <TournamentPairingsGrid/> internal state (#98) #93 * feat: #101 Add player count to roster (#103) * feat: Show full player names when tournaments require it * feat: Add activePlayerCount to deep tournaments * feat: Sort tournament competitors by name * task: Clean-up .card mixin (#102) #100 * feat: #99 Improve tournament competitor edit dialog (#104) * feat: #106 Improve signIn error handling (#107) * Update convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update convex/_model/users/_helpers/checkUserTournamentForcedName.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * feat: Hide players with 0 matches from rankings * feat: #112 Add more mercenary team options (#113) * feat: #110 Add manual table assignments (#111) * fix: Ensure round 0 rankings can be included * Refactor tournament actions (#114) * refactor: Improve tournament actions * chore: Clean-up Convex errors * fix: Do not try to clean up current round timer on tournament end * fix: Don't allow players to be removed from tournament * chore: Update test tournament banner image * Update TournamentCard.tsx * fix: Ensure round 0 rankings can be included * fix: Fix end tournament round context menu behavior * chore: Improve mock match result creation * feat: Allow matchResult.playedAt to be date string or number * feat: #115 Hide match result battle plans (#116) * feat: Set page title based on <PageWrapper/> title prop * fix: Use <IdentityBadge/> to fix player name spacing on match results * fix: Correctly include match results relevant to a tournament * feat: #57 Implement basic dashboard (#119) * fix: Remove double border on dashboard sections * fix: Add key to dashboard tournaments * feat: Improve <TournamentPairingRow/> styling * fix: Remove extraneous error message * fix: Improve <Form/> isDirty calculation * fix: Render all competitors in <TournamentCompetitorForm/> * fix: Also show empty state if rankings are empty --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Summary by CodeRabbit
New Features
Enhancements
Bug Fixes
Refactor
Removals
Documentation